package ltd.nullpointer.tcp.core.util;

import com.boot2.core.utils.StringUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 目录，jar包，类扫描器
 * 
 * @author zhangweilin
 * @date 2015年12月1日 下午5:00:58
 * @version V1.0
 * @Description
 */
public class ClassPathPackageScanner {
	private String basePackage;
	private ClassLoader classLoader;

	/**
	 * Construct an instance and specify the base package it should scan.
	 * 
	 * @param basePackage
	 *            The base package to scan.
	 */
	public ClassPathPackageScanner(String basePackage) {
		this.basePackage = basePackage;
		this.classLoader = getClass().getClassLoader();

	}

	/**
	 * Construct an instance with base package and class loader.
	 * 
	 * @param basePackage
	 *            The base package to scan.
	 * @param cl
	 *            Use this class load to locate the package.
	 */
	// public ClassPathPackageScanner(String basePackage, ClassLoader cl)
	// {
	// this.basePackage = basePackage;
	// this.classLoader = cl;
	// }

	/**
	 * Get all fully qualified names located in the specified package and its
	 * sub-package.
	 *
	 * @return A list of fully qualified names.
	 * @throws IOException
	 */
	public static List<String> getFullyQualifiedClassNameList(String basePackage) throws IOException {
		// System.out.println("开始扫描包{}下的所有类: " + basePackage);
		List<String> classNameList = new ArrayList<String>();
		doScan(basePackage, classNameList);
		return classNameList;
	}

	/**
	 * @param packageName
	 * @return
	 */
	public static Set<Class<?>> getClassesByPackageName(String packageName) {
		Set<Class<?>> classeSet = new HashSet<>();
		try {
			List<String> classNameList = ClassPathPackageScanner.getFullyQualifiedClassNameList(packageName);
			for (String className : classNameList) {
				Class<?> clazz = Class.forName(className);
				classeSet.add(clazz);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return classeSet;
	}

	/**
	 * @param packageName
	 * @return
	 */
	public static Set<Class<?>> getClassesByPackageName(String packageName,Class<? extends Annotation> clazz0) {
		Set<Class<?>> classeSet = new LinkedHashSet<>();
		try {
			List<String> classNameList = ClassPathPackageScanner.getFullyQualifiedClassNameList(packageName);
			for (String className : classNameList) {
				Class<?> clazz = Class.forName(className);
				Annotation annotation0 = clazz.getAnnotation(clazz0);
				if (annotation0 != null) {
					classeSet.add(clazz);
				} 
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return classeSet;
	}

	/**
	 * Actually perform the scanning procedure.
	 *
	 * @param basePackage
	 * @param nameList
	 *            A list to contain the result.
	 * @return A list of fully qualified names.
	 * @throws IOException
	 */
	private static void doScan(String basePackage, List<String> allNameList) throws IOException {
		String splashPath = StringUtils.dotToSplash(basePackage, "xml", "ftl");
		// URL url =
		// Thread.currentThread().getContextClassLoader().getResource(splashPath);
		Enumeration<URL> urlArr = Thread.currentThread().getContextClassLoader().getResources(splashPath);
		while (urlArr.hasMoreElements()) {
			URL url = urlArr.nextElement();
			// System.out.println("urlArr: " + url);
			String filePath = StringUtils.getRootPath(url);
			scanByUrl(basePackage, splashPath, filePath, allNameList);
		}
	}

	private static List<String> scanByUrl(String basePackage, String splashPath, String filePath,
			List<String> allNameList) throws IOException {
		// Get classes in that package.
		// If the web server unzips the jar file, then the classes will exist in
		// the form of
		// normal file in the directory.
		// If the web server does not unzip the jar file, then classes will
		// exist in jar file.
		List<String> names = null; // contains the name of the class file. e.g.,
									// Apple.class will be stored as "Apple"
		boolean isJarFile = false;
		if (isJarFile(filePath)) {
			// jar file
			// System.out.println("是一个JAR包: " + filePath);

			names = readFromJarFile(filePath, splashPath);
			isJarFile = true;
		} else if (isIgnoreFile(filePath)) {
			// System.out.println("是一个xml,忽略: " + filePath);
		} else {
			// directory
			// System.out.println("是一个目录: " + filePath);
			names = readFromDirectory(filePath);
		}

		if (null != names && !names.isEmpty()) {
			for (String name : names) {
				if (isClassFile(name)) {
					// nameList.add(basePackage + "." +
					// StringUtil.trimExtension(name));
					// System.out.println("name2: " + name);
					// nameList.add(toFullyQualifiedName(name, basePackage));
					String className = null;
					if (isJarFile) {
						className = StringUtils.trimExtension(StringUtils.splashToDot(name, "xml", "ftl"));
					} else {
						className = toFullyQualifiedName(name, basePackage);
					}
					allNameList.add(className);
				} else {
					// this is a directory
					// check this directory for more classes
					// do recursive invocation
					doScan(basePackage + "." + name, allNameList);
				}
			}
		}
		return allNameList;
	}

	private static boolean isIgnoreFile(String filePath) {
		String[] suffixArr = { "xml", "ftl" };
		for (String suffix : suffixArr) {
			if (filePath.endsWith(suffix)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Convert short class name to fully qualified name. e.g., String ->
	 * java.lang.String
	 */
	private static String toFullyQualifiedName(String shortName, String basePackage) {
		StringBuilder sb = new StringBuilder(basePackage);
		sb.append('.');
		sb.append(StringUtils.trimExtension(shortName));

		return sb.toString();
	}

	private static List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {

		System.out.println("从JAR包中读取类: {}: " + jarPath);

		JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
		JarEntry entry = jarIn.getNextJarEntry();

		List<String> nameList = new ArrayList<>();
		while (null != entry) {
			String name = entry.getName();
			if (name.startsWith(splashedPackageName) && isClassFile(name)) {
				nameList.add(name);
			}

			entry = jarIn.getNextJarEntry();
		}

		return nameList;
	}

	private static List<String> readFromDirectory(String path) {
		File file = new File(path);
		String[] names = file.list();

		if (null == names) {
			return null;
		}

		return Arrays.asList(names);
	}

	private static boolean isClassFile(String name) {
		return name.endsWith(".class");
	}

	private static boolean isJarFile(String name) {
		return name.endsWith(".jar");
	}

	// /**
	// * For test purpose.
	// */
	// public static void main(String[] args) throws Exception
	// {
	// ClassPathPackageScanner scan = new
	// ClassPathPackageScanner("com.mysql.jdbc");
	// scan.getFullyQualifiedClassNameList();
	// }

}
